home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 5
/
Apprentice-Release5.iso
/
Source Code
/
C++
/
Applications
/
PICSee Dust 1.01
/
Quaternary Source
/
SoundUtils.c
< prev
next >
Wrap
Text File
|
1995-11-23
|
19KB
|
673 lines
/*
File: SoundUtils.c
Written by Hiep Dam.
Based on code written by Brigham Stevens, develop 17 (GameSounds.c)
Herein lies the "core" sound routines generic enuf to be usable by anyone
and any application.
Last Update: Oct 1995
Version 1.1 (Oct 26 95) [HTD]: Compiled with PowerPC, updated CreateSndChannel
to be compatible with PPC via call to NewSndCallBackProc & addition of
global UPP sSndCallBackProc.
*/
#include <Sound.h>
#include "SoundUtils.h"
enum {
kSndIdle = 0, // No sound is playing, and sound has been purged
kSndDone = -1 // Sound done playing, and needs to be unlocked & purged
};
// Our private data structure, used to hold the sound channel,
// as well as other info pertaining to the channel
typedef struct {
SndChannelPtr chan;
Handle snd;
short priority; // Either kSndIdle, kSndDone, or a number > 0
short padding;
} SndChanInfo, *SndChanInfoPtr;
// Our global storage of channel data (4 max)
static SndChanInfo gSndChans[kMaxChans] =
{ nil, nil, kSndIdle, 0,
nil, nil, kSndIdle, 0,
nil, nil, kSndIdle, 0,
nil, nil, kSndIdle, 0 };
// Sound Manager Version miscellany
#define kEnhancedSoundMgr 3
static unsigned char gSoundMgrVersion;
static char padding_char;
static short padding_short;
static SndCallBackUPP sSndCallBackProc = NULL;
//#define REINIT_CHANNEL
// ---------------------------------------------------------------------------
// Our callback procedure.
pascal void SndDoneProc(SndChannelPtr chan, SndCommand *cmd);
// ---------------------------------------------------------------------------
void InitSoundUtils() {
/*
This sets up some internal variables; you should call this
before using any other calls in SoundUtils.c, else some
strange things might happen...
*/
//gSoundMgrVersion = SndSoundManagerVersion().majorRev;
gSoundMgrVersion = SndSoundManagerVersion();
sSndCallBackProc = NewSndCallBackProc(SndDoneProc);
} // END InitSoundUtils
// ---------------------------------------------------------------------------
OSErr CreateSndChannel(short whichChan) {
/*
Create a sound channel. If the channel whichChan is currently
occupied, it disposes it first before allocating a new channel.
*/
OSErr err;
if (gSndChans[whichChan].chan != nil)
err = DisposeSndChannel(whichChan);
// Ah, for the love of PPC!
// If the snd callback proc hasn't been assigned yet, do it now
if (sSndCallBackProc == NULL)
sSndCallBackProc = NewSndCallBackProc(SndDoneProc);
err = SndNewChannel(&gSndChans[whichChan].chan, sampledSynth,
initMono + initNoDrop + initNoInterp, sSndCallBackProc);
return(err);
} // END CreateSndChannel
// ---------------------------------------------------------------------------
OSErr DisposeSndChannel(short whichChan) {
/*
Dispose a sound channel and resets some internal data.
If the channel is already disposed, it just exits.
*/
OSErr err;
if (gSndChans[whichChan].chan == nil)
return(noErr);
err = SndDisposeChannel(gSndChans[whichChan].chan, true);
// Reset our internal data, flags...
if (err == noErr) {
gSndChans[whichChan].chan = nil;
gSndChans[whichChan].snd = nil;
gSndChans[whichChan].priority = kSndIdle;
}
return(err);
} // END DisposeSndChannel
// ---------------------------------------------------------------------------
void PlayAsynch(Handle sndHdl, short whichChan) {
/*
Play a sound asynchronously, using SndPlay.
NOTE: The channel specified in whichChan must be
already allocated via CreateSndChannel(). No checking
is done.
*/
OSErr err;
#ifdef REINIT_CHANNEL
if (gSoundMgrVersion < kEnhancedSoundMgr) {
err = DisposeSndChannel(whichChan);
if (err != noErr)
return;
err = CreateSndChannel(whichChan);
if (err != noErr)
return;
}
#endif
HLock(sndHdl);
err = SndPlay(gSndChans[whichChan].chan, (SndListHandle)sndHdl, true); // Simple, no?
} // END PlayAsynch
// ---------------------------------------------------------------------------
void PlayAsynchBuffer(Handle sndHdl, short whichChan) {
/*
This routine plays a sound asynchronously, using
bufferCmds and SndDoImmediate, rather than SndPlay.
*/
SoundHeader *sndDataOffset;
long offset;
SndCommand sndCmd;
OSErr err;
// First, lock the sucker.
if (sndHdl == nil) return;
HLockHi(sndHdl);
#ifdef REINIT_CHANNEL
if (gSoundMgrVersion < kEnhancedSoundMgr) {
err = DisposeSndChannel(whichChan);
if (err != noErr) {
HUnlock(sndHdl); return;
}
err = CreateSndChannel(whichChan);
if (err != noErr) {
HUnlock(sndHdl); return;
}
}
#endif
// Get the offset into the sound resource, wherein the
// sound header lies...
err = RetrieveSndHeaderOffset(sndHdl, &offset);
if (err != noErr) {
HUnlock(sndHdl);
return;
}
// The bufferCmd requires we give it a pointer to a sound
// header, which lies somewhere in the sound resource.
// We retrieve this location by determining the offset into
// the resource via RetrieveSndHeaderOffset, add it to
// the beginning address of the sound, and voila!
sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
// Stuff in the parameters so bufferCmd can do its thing...
sndCmd.cmd = bufferCmd;
sndCmd.param1 = 0;
sndCmd.param2 = (long)sndDataOffset;
// Push it into the sound channel queue...
err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
} // END PlayAsycnchBuffer
// ---------------------------------------------------------------------------
void SndChanStop(SndChannelPtr chan) {
/*
Use this to stop any sound currently playing.
It does this by first telling the sound channel to
stop (quiet) and removes any other sounds that
may have been queued in the channel (flush), else
those might start playing...
*/
SndCommand sndCmd;
OSErr err;
sndCmd.cmd = quietCmd;
sndCmd.param1 = sndCmd.param2 = 0; // Unused
err = SndDoImmediate(chan, &sndCmd);
if (err == noErr) {
sndCmd.cmd = flushCmd;
err = SndDoImmediate(chan, &sndCmd);
}
} // END SndChanStop
void SndStop(short whichChan) {
SndChanStop(gSndChans[whichChan].chan);
} // END SndStop
// ---------------------------------------------------------------------------
void SndStopSoftly(short whichChan) {
if (!SndDone(whichChan)) {
short saveAmp, i;
long dummy;
saveAmp = SndGetAmplitude(whichChan);
i = saveAmp;
while (i > 0) {
SndSetAmplitude(whichChan, i);
i -= 10;
Delay(1, &dummy);
if (i < 0) break;
}
// Stop it now.
SndStop(whichChan);
// Restore amplitude, since setting amplitude with no
// sound playing will apply it to next sound played...
SndSetAmplitude(whichChan, saveAmp);
}
} // END SndStopSoftly
// ---------------------------------------------------------------------------
void WaitTillSndDone(short whichChan) {
while (!SndDone(whichChan)) {
// Do nothing
}
} // END WaitTillSndDone
// ---------------------------------------------------------------------------
Boolean SndDone(short whichChan) {
/*
Tells you whether the sound is done playing
(i.e. whether the sound channel is idle or not).
Specify which channel...
*/
return(SndChanDone(gSndChans[whichChan].chan));
} // END SndDone
Boolean SndChanDone(SndChannelPtr chan) {
/*
Same as SndDone, but you can pass any 'ol sound channel,
not just our private array of snd channels...
*/
OSErr err;
SCStatus status;
// Poll the channel
err = SndChannelStatus(chan, sizeof(SCStatus), &status);
if (err == noErr)
// If the channel is busy, then the sound is NOT done,
// else the sound is done (and the channel is idle)...
return(!status.scChannelBusy);
else
return(false); // Hmm. Error!
} // END SndChanDone
// ---------------------------------------------------------------------------
short SndChanGetAmplitude(SndChannelPtr chan) {
/*
Get the amplitude of the sound currently being played
in chan. If an error occurred, will return -1, else
a value in the range 0..255
*/
SndCommand sndCmd;
OSErr err;
short amp;
sndCmd.cmd = getAmpCmd;
sndCmd.param1 = 0; // Unused
sndCmd.param2 = (long)&
err = SndDoImmediate(chan, &sndCmd);
if (err != noErr)
return(-1);
else
return(amp);
} // END SndChanGetAmplitude
short SndGetAmplitude(short whichChan) {
return(SndChanGetAmplitude(gSndChans[whichChan].chan));
} // END SndGetAmplitude
// ---------------------------------------------------------------------------
void SndChanSetAmplitude(SndChannelPtr chan, short amp) {
/*
Use this to change the amplitude (loudness) of the sound being
currently played in the sound channel. Note, if no sound
is playing this sets the amplitude of the next sound to
be played.
The amplitude range in "amp" should be in the range 0..255
*/
SndCommand sndCmd;
OSErr err;
if (chan != nil) {
sndCmd.cmd = ampCmd;
sndCmd.param1 = amp;
sndCmd.param2 = 0;
}
err = SndDoImmediate(chan, &sndCmd);
} // END SndChanSetAmplitude
void SndSetAmplitude(short whichChan, short amp) {
SndChanSetAmplitude(gSndChans[whichChan].chan, amp);
} // END SndSetAmplitude
// ---------------------------------------------------------------------------
// Format 1 of 'snd ' rsrc header
typedef struct {
short format, numSynths;
} Snd1Header, *Snd1HeaderPtr;
// Format 2 of 'snd ' rsrc header
typedef struct {
short format, refCount;
} Snd2Header, *Snd2HeaderPtr;
OSErr RetrieveSndHeaderOffset(Handle sndHdl, long *offset) {
/*
This routine is used to find the offset of a handle to a sound
into the sound's header. This routine can be used either
with the older Sound Manager or the newer 3.0 version
(it calls GetSoundHeaderOffset() if using 3.0, else it
steps through the muck itself under older versions).
NOTE: Make sure you lock 'sndHdl' before passing it to
RetrieveSndHeaderOffset...
*/
Ptr myPtr; // To navigate resource
long myOffset; // Offset into resource
short numSynths; // Info about resource
short numCmds; // Ditto.
Boolean isDone; // Are we done yet?
OSErr myErr; // Oooh...
if (gSoundMgrVersion >= kEnhancedSoundMgr)
// Using new sound manager, our work is done for us...
#ifdef __MWERKS__
return(GetSoundHeaderOffset((SndListHandle)sndHdl, offset));
#else
// I'm assuming if this is not MetroWerks, it is an older
// Symantec C++ (non-Universal headers) so here is the
// workaround. Delete this if you have the Universal
// headers [yeah I have an older version of SymC++; who
// wants to pay $200+ for a friggin' upgrade?!?]
return(20);
#endif
else {
// Hmm. Have to do this crap ourselves...
// Initialize variables
myOffset = 0; // Return 0 if no snd header found
myPtr = (Ptr)*sndHdl; // Point to start of rsrc data
isDone = false; // Haven't yet found snd header
myErr = noErr;
// This thing doesn't work; so I'll just assume it's a
// Type 1 sound (system) and set the offset as 20. If
// it's type 2 (hypercard) it won't work, since the offset
// is 14...
*offset = 20;
return(myErr);
// Skip everything before sound commands
switch(((Snd1HeaderPtr)myPtr)->format) {
// Format 1 'snd ' resource
case firstSoundFormat:
// Skip header start, synth id, etc.
numSynths = ((Snd1HeaderPtr)myPtr)->numSynths;
myPtr += sizeof(Snd1Header);
myPtr += numSynths + sizeof(short) + sizeof(long);
break;
// Format 2 'snd ' resource
case secondSoundFormat:
myPtr += sizeof(Snd2Header);
break;
// Unrecognized format
default:
myErr = badFormat;
isDone = true;
break;
} // END switch
// Find number of commands and move to start of first cmd
numCmds = *(short*)myPtr;
myPtr += sizeof(short);
// Search for bufferCmd or soundCmd to obtain sound header
while ((numCmds >= 1) && !isDone) {
if ((*(short*)myPtr == bufferCmd + dataOffsetFlag ||
*(short*)myPtr == soundCmd + dataOffsetFlag)) {
myOffset = ((SndCommand*)myPtr)->param2;
isDone = true;
}
else {
myPtr += sizeof(SndCommand);
numCmds--;
}
} // END while
*offset = myOffset;
return(myErr);
}
} // END RetrieveSndHeaderOffset
// ---------------------------------------------------------------------------
SoundHeader *GetSoundHeader(Handle sndHdl) {
/*
Obtain a pointer to the sound header of a sound resource. Duh!
NOTE: It is up to you to lock the handle to the sound.
Unlock it ONLY after you a done with the pointer returned
by GetSoundHeader (if the sound not locked & becomes relocated,
then your sound header pointer may point to trash!)
*/
long offset;
OSErr err;
if (sndHdl == nil) return(nil);
err = RetrieveSndHeaderOffset(sndHdl, &offset);
if (err != noErr)
return(nil);
else
return((SoundHeader*)(StripAddress(*sndHdl) + offset));
} // END GetSoundHeader
// ==========================================================================
/*
All the following routines use the callback mechansim:
You play a sound, install a callback, and call a routine
to check the callback status during your idle loop.
(use PlayAsynchCallback and CheckSounds).
Before playing, the sound is locked down. Thus it must be
unlocked eventually. But how do you know when the sound is done?
There are two ways. One is the callback method. Install a
callback procedure, and it's called once the sound is done.
The callback sets a flag, and exits. Your periodically-invoked
idle procedure then checks this flag, and if set, interprets this
to mean the sound is done playing; it unlocks and disposes
of the sound.
Why can't we dispose of the sound in the callback, instead of
having to call our checking routine periodically? Because
callbacks have to stay within the rules of a routine that
is called during interrupt time: you cannot use any routines
that may move memory. Thus this semi-roundabout method...
There is one limitation with this method, however. Normally,
you can queue sounds to be played in a sound channel by calling
several bufferCmds (or SndPlays for that matter) one after
another, consecutively, with the same or different sounds.
You cannot do this using this mechanism! Because when you
call it, it stores whatever handle to that sound into a private
data structure, along with the sound channel. Later when the
sound is done, this sound handle is purged. But if you queue
another sound to be played (and the current sound isn't done)
then the new sound handle is stuffed into the private data
structure, losing our reference to the old sound, the one
being played. So you have to wait until the sound is done
(and it's been purged via CheckSounds before playing another
sound.
The solution: Try PlayAsynchCallbackPriority(), which takes
a priority. If the new priority is higher than the priority
of the sound being played (if any) then this routine takes
the time to stop the sound and purge the old sound handle
before playing the new sound...
*/
OSErr PlayAsynchCallback(Handle sndHdl, short whichChan) {
SoundHeader *sndDataOffset;
SndCommand sndCmd;
long offset;
OSErr err;
if (sndHdl == nil) return(-1972);
HLockHi(sndHdl);
#ifdef REINIT_CHANNEL
if (gSoundMgrVersion < kEnhancedSoundMgr) {
err = DisposeSndChannel(whichChan);
if (err != noErr) {
HUnlock(sndHdl); return(err);
}
err = CreateSndChannel(whichChan);
if (err != noErr) {
HUnlock(sndHdl); return(err);
}
}
#endif
err = RetrieveSndHeaderOffset(sndHdl, &offset);
if (err != noErr) {
HUnlock(sndHdl);
return(err);
}
sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
sndCmd.cmd = bufferCmd;
sndCmd.param1 = 0;
sndCmd.param2 = (long)sndDataOffset;
err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
if (err == noErr) {
// Install the callback
gSndChans[whichChan].snd = sndHdl;
gSndChans[whichChan].priority = 1;
sndCmd.cmd = callBackCmd;
sndCmd.param1 = 0;
sndCmd.param2 = (long)&gSndChans[whichChan];
err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
}
return(err);
} // END PlayAsynchCallback
// ---------------------------------------------------------------------------
OSErr PlayAsynchCallbackPriority(Handle sndHdl, short whichChan, short priority) {
SoundHeader *sndDataOffset;
SndCommand sndCmd;
long offset;
OSErr err;
if (sndHdl == nil) return(-1972);
HLockHi(sndHdl);
// First, check priority.
if (gSndChans[whichChan].priority == kSndIdle ||
(gSndChans[whichChan].priority) != kSndIdle &&
(priority >= gSndChans[whichChan].priority)) {
// OK, new sound has same or higher priority, and a sound
// is currently playing. Stop the sound...
if (gSndChans[whichChan].priority != kSndIdle)
SndChanStop(gSndChans[whichChan].chan);
#ifdef REINIT_CHANNEL
// If we're using the old sound manager (bummer!) we
// have to allocate a new channel [and destroy the old one]
// before playing a new sound. Yuck!
if (gSoundMgrVersion < kEnhancedSoundMgr) {
err = DisposeSndChannel(whichChan);
if (err != noErr) {
HUnlock(sndHdl); return(err);
}
err = CreateSndChannel(whichChan);
if (err != noErr) {
HUnlock(sndHdl); return(err);
}
}
#endif
// Time to play the sound...
err = RetrieveSndHeaderOffset(sndHdl, &offset);
if (err != noErr) {
HUnlock(sndHdl);
return(err);
}
sndDataOffset = (SoundHeader*)(StripAddress(*sndHdl) + offset);
sndCmd.cmd = bufferCmd;
sndCmd.param1 = 0;
sndCmd.param2 = (long)sndDataOffset;
// Play the sound
err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
// Install the callback
if (err == noErr) {
// Install the callback
gSndChans[whichChan].snd = sndHdl;
gSndChans[whichChan].priority = priority;
sndCmd.cmd = callBackCmd;
sndCmd.param1 = 0;
sndCmd.param2 = (long)&gSndChans[whichChan];
err = SndDoCommand(gSndChans[whichChan].chan, &sndCmd, false);
}
}
return(err);
} // END PlayAsynchCallbackPriority
// ---------------------------------------------------------------------------
pascal void SndDoneProc(SndChannelPtr chan, SndCommand *cmd) {
/*
This is the callback routine. It just sets the
priority to -1, meaning a sound is done playing.
*/
SndChanInfo *sndChan;
sndChan = (SndChanInfo*)cmd->param2;
sndChan->priority = kSndDone;
} // END SndDoneProc
// ---------------------------------------------------------------------------
void CheckSounds(short whichChan, SndCleanupProc cleaner) {
/*
Call this within your idle loop. It checks the sound channels,
and disposes of sounds that have been already played, freeing
up memory.
Pass -1 in argument whichChan to check all sound channels...
You can pass to CheckSounds() the address of a cleanup routine,
which should do whatever you need it to do after you're done
with a sound (such as unlocking it and disposing of the sound).
You can use DefaultSndCleaner(), which unlocks and purges the
sound. Or pass nil and CheckSounds() will not do anything with
your sounds (perhaps you want to keep them locked, for example).
*/
short i, max;
if (whichChan == -1) {
i = 0;
max = kMaxChans;
}
else {
i = whichChan;
max = whichChan + 1;
}
for (i; i < max; i++) {
if (gSndChans[i].chan != nil && gSndChans[i].priority == kSndDone) {
// Sound is done playing. Invoke cleaning routine, if any.
if (cleaner)
(*cleaner)(gSndChans[i].snd);
gSndChans[i].priority = kSndIdle;
}
}
} // END CheckSounds
// ---------------------------------------------------------------------------
void DefaultSndCleaner(Handle snd) {
HUnlock(snd);
HPurge(snd);
} // END DefaultSndCleaner